using System;
using System.Xml;
using System.Text;
using System.Globalization;

namespace Waymex.Gps
{
	/// <summary>
	/// Collection of Route objects.
	/// </summary>
	/// <remarks>
	/// <para>
	/// When the Host requests the GPS to send routes, the GPS will send every route stored in its
	/// database. When the Host sends routes to the GPS, the Host may selectively transfer any
	/// number of routes.
	/// </para>
	/// <para>
	/// Some GPS devices contain an internal database of waypoint information; for example, most
	/// aviation products have an internal database of aviation waypoints, and others may have an
	/// internal database of land waypoints. When routes are being transferred from the Host to one
	/// of these units, the GPS will attempt to match the incoming route waypoints with waypoints in
	/// its internal database. The GPS inspects the 'Class' member of the incoming route waypoint
	/// and if it indicates a non-user waypoint, the GPS searches its internal database using either
	/// the 'Identifier' and 'Country' members or the 'Sub Class' member. If a match is found, the
	/// waypoint from the internal database is used for the route; otherwise, a new user waypoint is
	/// created and used.
	/// </para>
	/// </remarks>
	public class RouteCollection : System.Collections.IEnumerable
	{
		
		private const string ERR_MSG_4000 = "Function not available in Evaluation Version.";
		
		private System.Collections.ArrayList mcRoutes;

		private int m_hashCode = -1;

		private const string XML_ROOT = "gpsroutes";
		private const string XML_ROUTE = "gpsroute";

		/// <summary>
		/// Constructor.
		/// </summary>
		public RouteCollection() : base()
		{
			mcRoutes = new System.Collections.ArrayList();
		}
		/// <summary>
		/// Adds a Route object to the RouteCollection object. 
		/// </summary>
		public void Add(Route route)
		{
			try
			{
				mcRoutes.Add(route);
			}
			catch (Exception e)
			{
				throw new InvalidDataException(e.Message, e);
			
			}
		}
		/// <summary>
		/// Returns the number of Route objects in the collection. 
		/// </summary>
		public int Count
		{
			get
			{
				try
				{
					return mcRoutes.Count;
				}
				catch (Exception e)
				{
					throw new InvalidDataException(e.Message, e);
				}
			}
		}
		/// <summary>
		/// Returns the Enumerator.
		/// </summary>
		public System.Collections.IEnumerator GetEnumerator()
		{
			return mcRoutes.GetEnumerator();
		}
		/// <summary>
		/// Can be used to refer to a member of the collection by ordinal reference. 
		/// This Property is the default indexer property in C#.
		/// </summary>
		public Route this[int index]
		{
			get
			{
				return (Route)mcRoutes[index];
			}

		}
        /// <summary>
        /// Returns an XML representation of the object.
        /// </summary>
        public string ToXml()
        {
            return ToXml(String.Empty);
        }
        /// <summary>
        /// Returns an XML representation of the object.
        /// This overloaded method accepts an xslt filename which can be used to ransform the xml.
        /// </summary>
        public string ToXml(string xsltFilename)
        {
			StringBuilder strbldXML = new StringBuilder("");
            String xml = String.Empty;

			try
			{
				strbldXML.Append ("<");
				strbldXML.Append (XML_ROOT);
				strbldXML.Append (">");

				foreach(Route objRoute in mcRoutes)
				{
					strbldXML.Append(objRoute.ToXml());
				}

				strbldXML.Append ("</");
				strbldXML.Append (XML_ROOT);
				strbldXML.Append (">");

                //transform if required
                xml = strbldXML.ToString();

                if (xsltFilename.Length > 0)
                {
                    xml = Waymex.Xml.Transform.XsltTransform(xml, xsltFilename);
                }
            }

            catch (NullReferenceException e)
            { 
				throw new XmlException(e.Message, e);

			}

			return xml;
		}

		/// <summary>
		/// This method populates the object from XML.
		/// </summary>
        public void XmlLoad(string xml)
		{

			XmlDocument objDOM = new XmlDocument();

			try
			{
                objDOM.LoadXml(xml);

				if(objDOM.FirstChild.Name == XML_ROOT)
				{
					foreach(XmlNode objNode in objDOM.FirstChild.ChildNodes)
					{
						try
						{
							switch(objNode.Name)
							{
								case XML_ROUTE:

									Route objRoute = new Route();
									objRoute.XmlLoad(objNode.OuterXml);
									mcRoutes.Add(objRoute);
									break;
							
							}
						}
                        catch (NullReferenceException ex)
						{
						}
					}
				}	
			}
			catch(Exception e)
			{
				throw new XmlException(e.Message, e);
			}
		}
		private static string ByteToHex(byte[] ByteData)
		{
			//---------------------------------------------------------------------------
			//	DESCRIPTION:	This function converts an array of bytes to a string
			//					of hex digits representing those bytes.
			//
			//
			//	PARAMETERS:
			//				ByteData()  Array of bytes.
			//
			//	RETURNS:
			//				String      String of hex character pairs representing
			//							each byte in the byte array, separated
			//							with a space.
			//
			//---------------------------------------------------------------------------
		
			string sTemp = "";
			string sMessage = "";

				if(ByteData != null)
				{
					for(int f = 0; f < ByteData.Length; f++)
					{
						sTemp = ByteData[f].ToString("X"); //hex returns a string representing the passed number

						if(sTemp.Length < 2)
						{
							sTemp = string.Concat("0", sTemp);
						}
						
						sMessage = string.Concat(sMessage, " ", sTemp);
					}
					
					return sMessage.Trim();
				}
				else
				{
					return "";
				}
		}
		/// <summary>
		/// This method returns a System.Data.Dataset populated with the contents of the RouteCollection object.
		/// The dataset includes two tables called 'Routes' and 'Waypoints'. A 'Route ID' field has been added to
		/// each of the tables, it is used to create a relationship called 'Waypoints' between them.
		/// </summary>
		public System.Data.DataSet ToDataSet()
		{
			//constants for the dataset
			const string DS_ROUTES = "Routes";
			const string DS_RELATION_ROUTE_WAYPOINT = "Waypoints";
			//constants for the route table
			const string DS_TABLE_ROUTES = "Routes";
			const string DS_FIELD_ROUTE_ID = "Route ID"; //PK
			const string DS_FIELD_ROUTE_ID_TYPE = "System.Int32";
			const string DS_FIELD_ROUTE_NUMBER = "Number";
			const string DS_FIELD_ROUTE_NUMBER_TYPE = "System.Int16";
			const string DS_FIELD_ROUTE_IDENTIFIER = "Identifier";
			const string DS_FIELD_ROUTE_IDENTIFIER_TYPE = "System.String";
			const string DS_FIELD_ROUTE_COMMENT = "Comment";
			const string DS_FIELD_ROUTE_COMMENT_TYPE = "System.String";

			//constants for the waypoints table
			const string DS_TABLE_WAYPOINTS = "Waypoints";
			const string DS_FIELD_WAYPOINT_IDENTIFIER = "Identifier";
			const string DS_FIELD_WAYPOINT_IDENTIFIER_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_LATITUDE = "Latitude";
			const string DS_FIELD_WAYPOINT_LATITUDE_TYPE = "System.Double";
			const string DS_FIELD_WAYPOINT_LONGITUDE = "Longitude";
			const string DS_FIELD_WAYPOINT_LONGITUDE_TYPE = "System.Double";
			const string DS_FIELD_WAYPOINT_COMMENT = "Comment";
			const string DS_FIELD_WAYPOINT_COMMENT_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_SYMBOL = "Symbol";
			const string DS_FIELD_WAYPOINT_SYMBOL_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_COLOUR = "Colour";
			const string DS_FIELD_WAYPOINT_COLOUR_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_DISPLAY = "Display";
			const string DS_FIELD_WAYPOINT_DISPLAY_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_PROX_DIST = "Proximity Distance";
			const string DS_FIELD_WAYPOINT_PROX_DIST_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_PROX_INDEX = "Proximity Index";
			const string DS_FIELD_WAYPOINT_PROX_INDEX_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_ALTITUDE = "Altitude";
			const string DS_FIELD_WAYPOINT_ALTITUDE_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_DEPTH = "Depth";
			const string DS_FIELD_WAYPOINT_DEPTH_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_CLASS = "Class";
			const string DS_FIELD_WAYPOINT_CLASS_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_SUB_CLASS = "Sub Class";
			const string DS_FIELD_WAYPOINT_SUB_CLASS_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_ATTRIBUTES = "Attributes";
			const string DS_FIELD_WAYPOINT_ATTRIBUTES_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_LINK = "Link";
			const string DS_FIELD_WAYPOINT_LINK_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_STATE = "State";
			const string DS_FIELD_WAYPOINT_STATE_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_COUNTRY = "Country";
			const string DS_FIELD_WAYPOINT_COUNTRY_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_CITY = "City";
			const string DS_FIELD_WAYPOINT_CITY_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_ADDRESS = "Address";
			const string DS_FIELD_WAYPOINT_ADDRESS_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_FACILITY = "Facility";
			const string DS_FIELD_WAYPOINT_FACILITY_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_CROSS_ROAD = "Cross Road";
			const string DS_FIELD_WAYPOINT_CROSS_ROAD_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_UNUSED_1 = "Unused 1";
			const string DS_FIELD_WAYPOINT_UNUSED_1_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_UNUSED_2 = "Unused 2";
			const string DS_FIELD_WAYPOINT_UNUSED_2_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_PACKET_TYPE = "Packet Type";
			const string DS_FIELD_WAYPOINT_PACKET_TYPE_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_TIME_EN_ROUTE = "Time En Route";
			const string DS_FIELD_WAYPOINT_TIME_EN_ROUTE_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_TEMPERATURE = "Temperature";
			const string DS_FIELD_WAYPOINT_TEMPERATURE_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_CATEGORY = "Category";
			const string DS_FIELD_WAYPOINT_CATEGORY_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_TIMESTAMP = "Time Stamp";
			const string DS_FIELD_WAYPOINT_TIMESTAMP_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS = "Route Link Class";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_ID = "Route Link Identifier";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_ID_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS = "Route Link Sub Class";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS_TYPE = "System.String";

			//create a new data set
			System.Data.DataSet ds = new System.Data.DataSet(DS_ROUTES);
            ds.Locale = CultureInfo.InvariantCulture;
						
			//create the routes and waypoints table			
			System.Data.DataTable dtR = new System.Data.DataTable (DS_TABLE_ROUTES);
			System.Data.DataTable dtW = new System.Data.DataTable(DS_TABLE_WAYPOINTS);
            dtR.Locale = CultureInfo.InvariantCulture;
            dtW.Locale = CultureInfo.InvariantCulture;

			//add the columns to the routes table
			dtR.Columns.Add(DS_FIELD_ROUTE_ID, Type.GetType(DS_FIELD_ROUTE_ID_TYPE));
			dtR.Columns.Add(DS_FIELD_ROUTE_NUMBER, Type.GetType(DS_FIELD_ROUTE_NUMBER_TYPE));
			dtR.Columns.Add(DS_FIELD_ROUTE_IDENTIFIER, Type.GetType(DS_FIELD_ROUTE_IDENTIFIER_TYPE));
			dtR.Columns.Add(DS_FIELD_ROUTE_COMMENT, Type.GetType(DS_FIELD_ROUTE_COMMENT_TYPE));

			//add the columns to the waypoints table
			dtW.Columns.Add(DS_FIELD_ROUTE_ID, Type.GetType(DS_FIELD_ROUTE_ID_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_IDENTIFIER, Type.GetType(DS_FIELD_WAYPOINT_IDENTIFIER_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_LATITUDE, Type.GetType(DS_FIELD_WAYPOINT_LATITUDE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_LONGITUDE, Type.GetType(DS_FIELD_WAYPOINT_LONGITUDE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_COMMENT , Type.GetType(DS_FIELD_WAYPOINT_COMMENT_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_SYMBOL, Type.GetType(DS_FIELD_WAYPOINT_SYMBOL_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_COLOUR, Type.GetType(DS_FIELD_WAYPOINT_COLOUR_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_DISPLAY, Type.GetType(DS_FIELD_WAYPOINT_DISPLAY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_PROX_DIST, Type.GetType(DS_FIELD_WAYPOINT_PROX_DIST_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_PROX_INDEX, Type.GetType(DS_FIELD_WAYPOINT_PROX_INDEX_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ALTITUDE, Type.GetType(DS_FIELD_WAYPOINT_ALTITUDE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_DEPTH, Type.GetType(DS_FIELD_WAYPOINT_DEPTH_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CLASS, Type.GetType(DS_FIELD_WAYPOINT_CLASS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_SUB_CLASS, Type.GetType(DS_FIELD_WAYPOINT_SUB_CLASS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ATTRIBUTES, Type.GetType(DS_FIELD_WAYPOINT_ATTRIBUTES_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_LINK, Type.GetType(DS_FIELD_WAYPOINT_LINK_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_STATE, Type.GetType(DS_FIELD_WAYPOINT_STATE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_COUNTRY, Type.GetType(DS_FIELD_WAYPOINT_COUNTRY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CITY, Type.GetType(DS_FIELD_WAYPOINT_CITY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ADDRESS, Type.GetType(DS_FIELD_WAYPOINT_ADDRESS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_FACILITY, Type.GetType(DS_FIELD_WAYPOINT_FACILITY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CROSS_ROAD, Type.GetType(DS_FIELD_WAYPOINT_CROSS_ROAD_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_UNUSED_1, Type.GetType(DS_FIELD_WAYPOINT_UNUSED_1_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_UNUSED_2, Type.GetType(DS_FIELD_WAYPOINT_UNUSED_2_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_PACKET_TYPE, Type.GetType(DS_FIELD_WAYPOINT_PACKET_TYPE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_TIME_EN_ROUTE, Type.GetType(DS_FIELD_WAYPOINT_TIME_EN_ROUTE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_TEMPERATURE, Type.GetType(DS_FIELD_WAYPOINT_TEMPERATURE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CATEGORY, Type.GetType(DS_FIELD_WAYPOINT_CATEGORY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_TIMESTAMP, Type.GetType(DS_FIELD_WAYPOINT_TIMESTAMP_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ROUTE_LINK_ID, Type.GetType(DS_FIELD_WAYPOINT_ROUTE_LINK_ID_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS, Type.GetType(DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS, Type.GetType(DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS_TYPE));

			//add the data
			int intRouteID = 0;
			foreach(Route objRoute in mcRoutes)
			{
				 
				intRouteID++;
				object[] objRow = new object[4];
				objRow[0] = intRouteID;
				objRow[1] = objRoute.Number;
				objRow[2] = objRoute.Identifier;
				objRow[3] = objRoute.Comment;
				dtR.Rows.Add(objRow);

				foreach(Waypoint objWaypoint in objRoute.Waypoints)
				{
					objRow = new object[32];
					objRow[0] = intRouteID;
					objRow[1] = objWaypoint.Identifier;
					objRow[2] = objWaypoint.Latitude;
					objRow[3] = objWaypoint.Longitude;
					objRow[4] = objWaypoint.Comment;
					objRow[5] = objWaypoint.Symbol;
					objRow[6] = objWaypoint.Colour;
					objRow[7] = objWaypoint.Display;
					objRow[8] = objWaypoint.ProximityDistance;
					objRow[9] = objWaypoint.ProximityIndex;
					objRow[10] = objWaypoint.Altitude;
					objRow[11] = objWaypoint.Depth;
					objRow[12] = objWaypoint.Class;
					objRow[13] = objWaypoint.Subclass.ToString();				//byte array in Waypoint object
					objRow[14] = objWaypoint.Attributes;
					objRow[15] = objWaypoint.Link;
					objRow[16] = objWaypoint.State;
					objRow[17] = objWaypoint.Country;
					objRow[18] = objWaypoint.City;
					objRow[19] = objWaypoint.Address;
					objRow[20] = objWaypoint.Facility;
					objRow[21] = objWaypoint.Crossroad;
					objRow[22] = objWaypoint.Unused1;
					objRow[23] = objWaypoint.Unused2;
					objRow[24] = objWaypoint.PacketType;
					objRow[25] = objWaypoint.TimeENRoute;
					objRow[26] = objWaypoint.Temperature;
					objRow[27] = objWaypoint.Category;
					objRow[28] = objWaypoint.Timestamp;
					objRow[29] = objWaypoint.RouteLinkIdentifier;
					objRow[30] = objWaypoint.RouteLinkClass;
					objRow[31] = objWaypoint.RouteLinkSubclass.ToString();		//byte array in Waypoint object

					dtW.Rows.Add(objRow);
				}
			}
			//tables populated so add the tables to the dataset
			ds.Tables.Add(dtR);
			ds.Tables.Add(dtW);

			//tables now populated so add the relationship
			System.Data.DataColumn parentCol = null;
			System.Data.DataColumn childCol = null;

			parentCol = ds.Tables[DS_TABLE_ROUTES].Columns[DS_FIELD_ROUTE_ID];
			childCol = ds.Tables[DS_TABLE_WAYPOINTS].Columns[DS_FIELD_ROUTE_ID];

			System.Data.DataRelation relRouteWaypoint = new System.Data.DataRelation(DS_RELATION_ROUTE_WAYPOINT, parentCol, childCol);
			ds.Relations.Add(relRouteWaypoint);

			return ds;
		}
        /// <summary>
        /// Overridden method. Returns true of the values of each
        /// of the properties are equal in value.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>boolean</returns>
        public override bool Equals(object obj)
		{
			try
			{
				RouteCollection objRoutes = null;

				//check the type first
				if(obj.GetType() != this.GetType() )
					return false;

				//type ok so cast
				objRoutes = (RouteCollection)obj;

				//if the number in each collection is different then exit
				int w1Count = mcRoutes.Count;
				int w2Count = objRoutes.Count;
				if(w1Count != w2Count)
					return false;

				//both with the same number of elements so chech each one in turn
				for( int index = 0; index < w1Count; index++ )
				{
					if( !objRoutes[index].Equals(mcRoutes[index]) )
						return false;
				}
			}
			catch
			{
				throw;
			}
			finally
			{}

			//if we got here then they must match
			return true;
		}
        /// <summary>
        /// Overridden function. Retrieves a value that indicates the hash code value for the object.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
		{
			//this value will be used for the overriden GetHashCode function and should
			//ensure that two object that are the same return the same Hash Code.
			//the hash code has to be imutable to is stored in a member variable.
			if( m_hashCode <= 0 )
				m_hashCode = this.ToXml().GetHashCode();
	
			return m_hashCode;
		}

	}
}

